#undef UNICODE
#define WIN32_LEAN_AND_MEAN
#include <ws2tcpip.h>
#include <stdio.h>
#include <string>
#include <list>
#include <ctime>
#include <iostream>
#include <iomanip>
#include <sstream>
#include "msglist.h"
#include "raii.h"
#pragma comment (lib, "Ws2_32.lib")
const char*HeaderContentTxtUtf8 = "Content-type: text/plain; charset=utf-8\r\n";
const char*HeaderContentHTMLUtf8 = "Content-type: text/html; charset=utf-8\r\n";
const char*HeaderContentJSUtf8 = "Content-type: text/javascript; charset=utf-8\r\n";
const char*CRLF = "\r\n";
void httpResp (std::stringstream&stream, int code, bool headers) {
	switch (code) {
	case 200: stream << "HTTP/1.1 200 OK\r\n"; break;
	case 400: stream << "HTTP/1.1 400 Bad Request\r\n"; break;
	case 401: stream << "HTTP/1.1 401 Unauthorized\r\n"; break;
	case 404: stream << "HTTP/1.1 404 Not Found\r\n"; break;
	case 501: stream << "HTTP/1.1 501 Not Implemented\r\n"; break;
	}
	if (!headers) stream << "\r\n";
}
int main (int argc, char **argv) {
	WSADATA wsaData;
	int
		iResult,
		iSendResult;

	SOCKET ListenSocket = INVALID_SOCKET;
	SOCKET ClientSocket = INVALID_SOCKET;

	bool run = true;

	unsigned int recvbuflen = 4096;
	char*recvbuf = new char [recvbuflen];

	if (WSAStartup (MAKEWORD (2, 2), &wsaData) != 0)
		return 1;
	WSACleaner c01;

	struct addrinfo*result = NULL;
	struct addrinfo hints;

	ZeroMemory (&hints, sizeof (hints));
	hints.ai_family = AF_INET;
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_protocol = IPPROTO_TCP;
	hints.ai_flags = AI_PASSIVE;

	if (getaddrinfo (NULL, "9001", &hints, &result) != 0) {
		return 1;
	}
	
	ListenSocket = socket (result->ai_family, result->ai_socktype, result->ai_protocol);
	if (ListenSocket == INVALID_SOCKET) {
		freeaddrinfo (result);
		return 1;
	}
	SocketCleaner c02 (ListenSocket);

	if (bind (ListenSocket, result->ai_addr, result->ai_addrlen) == SOCKET_ERROR) {
		freeaddrinfo (result);
		return 1;
	}
	freeaddrinfo (result);

	/*char const*const index = "\
HTTP/1.1 200 OK\r\n\
Content-type: text/plain; charset=utf-8\r\n\
";
	char const*const r400 = "\
HTTP/1.1 400 Bad Request\r\n\
";
	char const*const r501 = "\
HTTP/1.1 501 Not Implemented\r\n\
";
	char*response = new char[4096];

	SYSTEMTIME st;
	time_t timeA = 0;
	for (int i = 0; i < 5; i++) {
		while (timeA == std::time (0));
		timeA = std::time (0);
		GetSystemTime (&st);
		std::cout << std::hex << std::uppercase
			<< timeA << std::endl
			<< (__int64) timeA * 1000 << std::endl
			<< (__int64) timeA * 1000 + st.wMilliseconds << std::endl;
	}*/

	if (listen (ListenSocket, SOMAXCONN) == SOCKET_ERROR) {
		return 1;
	}

	msglist messages;

	while (run) {
		std::cout << ".";
		ClientSocket = accept (ListenSocket, NULL, NULL);
		std::cout << ".";
		if (ClientSocket == INVALID_SOCKET)
			return 1;
		SocketCleaner c03 (ClientSocket);

		memset (recvbuf, 0, recvbuflen);
		iResult = recv (ClientSocket, recvbuf, recvbuflen, 0);

		try {
			if (iResult > 0) {
				std::string
					request (recvbuf),
					method, resource, protocol ("HTTP/1.1"), body;
				unsigned int
					lineStart = 0,
					lineEnd = 0,
					alsp = 0,
					colon,
					sp = request.find (" "),
					nl = request.find ("\r\n"),
					nlnl = request.find ("\r\n\r\n");
				if (sp > nl)
					return 1;
				method = request.substr (0, sp);
				alsp = sp + 1;
				sp = request.find (" ", alsp);
				if (sp > nl)
					resource = request.substr (alsp, nl - alsp);
				else {
					resource = request.substr (alsp, sp - alsp);
					alsp = sp + 1;				
					protocol = request.substr (alsp, nl - alsp);
				}
				if (nlnl != -1 && nlnl + 4 < request.size())
					body = request.substr (nlnl + 4);

				//printf ("%s %s %s", method.c_str (), resource.c_str (), protocol.c_str ());
				std::cout << resource;

				while (true) {
					lineStart = lineEnd;
					lineEnd = request.find ("\r\n", lineStart);
					if (lineEnd == std::string::npos)
						throw 0; //Bad request
					if (lineStart == lineEnd)
						break;
					/*colon = request.find (':', lineStart);
					if (colon < lineEnd) {
						std::string
							key (request.substr (lineStart, colon - lineStart)),
							val (request.substr (colon + 1, lineEnd - colon - 1));
						std::cout << key << val << std::endl;
					}*/
					lineEnd += 2;
				}

				std::stringstream response;
				if (method == "GET") {
					if (resource == "/main") {
						httpResp (response, 200, true);
						response
							<< HeaderContentHTMLUtf8
							<< CRLF
							<< "<!DOCTYPE html>"
							<< "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">"
							<<   "<head>"
							<<     "<title>chat</title>"
							<<     "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />"
							<<     "<style type=\"text/css\">"
							<<     "</style>"
							<<   "</head>"
							<<   "<body>"
							<<     "<div id=\"stream\"></div>"
							<<     "<input id=\"msg\"/>"
							<<     "<script type=\"text/javascript\" src=\"/main.js\"></script>"
							<<   "</body>"
							<< "</html>";
					}
					else if (resource == "/all") {
						httpResp (response, 200, true);
						response
							<< HeaderContentTxtUtf8
							<< CRLF;
						messages.all (response);
					}
					else if (resource.find ("/upd") == 0) {
						unsigned int from;
						if (resource.size () != 12 || resource.find_first_not_of ("0123456789abcdefABCDEF", 4) != std::string::npos)
							from = -1;
						else {
							std::stringstream resEnd (resource.substr (4, 8));
							resEnd >> std::hex >> from;
						}
						httpResp (response, 200, true);
						response
							<< HeaderContentTxtUtf8
							<< CRLF;
						if (from != messages.num)
							messages.from (from, response);
					}
					else if (resource == "/echo") {
						httpResp (response, 200, true);
						response
							<< HeaderContentTxtUtf8
							<< CRLF
							<< request;
					}
					else if (resource == "/favicon.ico")
						httpResp (response, 404, false);

					else if (resource == "/main.js") {
						httpResp (response, 200, true);
						response
							<< HeaderContentJSUtf8
							<< "Cache-Control: no-cache\r\n"
							<< CRLF
							<< "function $(id) {return document.getElementById (id);}"
							<< "var now = '', stream = $('stream'), msg = $('msg');"
							<< "function send () {"
							<<   "var xhr = new XMLHttpRequest ();"
							<<   "xhr.open ('POST', '/new', true);"
							<<   "xhr.send (msg.value);"
							<<   "msg.value = '';"
							<< "}"
							<< "msg.onkeypress = function (event) {if ((event.which == 10 || event.which == 13)) send ();};"
							<< "function update_request () {"
							<<   "var xhr = new XMLHttpRequest ();"
							<<   "xhr.open ('GET', '/upd' + now, true);"
							<<   "xhr.onreadystatechange = function () {update_apply (xhr)};"
							<<   "xhr.send ();"
							<< "}"
							<< "function update_apply (xhr) {"
							<<   "if (xhr.readyState != 4 || xhr.status != 200)"
							<<	   "return;"
							<<   "if (xhr.responseText.length > 1) {"
							<<     "var A = 0, B = 0;"
							<<     "while (true) {"
							<<       "B = xhr.responseText.indexOf (\"\\n\", A);"
							<<       "if (B == -1) {now = xhr.responseText.substr (A); break;}"
							<<       "var new_post = document.createElement ('div');"
							<<       "new_post.innerHTML = xhr.responseText.substring (A, B);"
							<<       "stream.appendChild (new_post);"
							<<       "A = B + 1;"
							<<     "}"
							<<   "}"
							<<   "setTimeout ('update_request ()', 250);"
							<< "}"
							<< "update_request ()";
					}
					else if (resource == "/exit") {
						httpResp (response, 200, false);
						run = false;
					}
					else if (resource == "/.jpg")
						httpResp (response, 401, false);
					else
						httpResp (response, 400, false);
				}
				else if (method == "POST") {
					if (resource == "/new") {
						messages.add (body);
						httpResp (response, 200, false);
					}
					else if (resource == "/cmd") {
					}
					else
						httpResp (response, 501, false);
				}
				else if (method == "OPTIONS") {
					httpResp (response, 200, true);
					response
						<< "Allow: " << (resource == "/new" ? "POST" : "GET") << "\r\n"
						<< "Content-Length: 0\r\n"
						<< CRLF;
				}
				else {
					httpResp (response, 501, false);
				}
				iSendResult = send (ClientSocket, response.str().c_str(), response.str().size(), 0);
				if (iSendResult == SOCKET_ERROR) {
					return 1;
				}
			}
			else if (iResult < 0) {
				return 1;
			}
		}
		catch (...) {
			std::cout << "evil detected" << std::endl;
		}

		if (shutdown (ClientSocket, SD_SEND) == SOCKET_ERROR) {
			return 1;
		}
		std::cout << " done" << std::endl;
	}
	delete [] recvbuf;
	recvbuf = 0;
	return 0;
}